﻿/*
 * The copyright statement below is for NLog.
 * See http://nlog-project.org for more information.

Copyright (c) 2004-2011 Jaroslaw Kowalski <jaak@jkowalski.net>

All rights reserved.

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions 
are met:

* Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer. 

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution. 

* Neither the name of Jaroslaw Kowalski nor the names of its 
  contributors may be used to endorse or promote products derived from this
  software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AuthoritExtensibility;
using NLog;
using PluginLogger;
using Ait = Authorit.API;

namespace SyncHeadings
{
	/// <summary>
	/// Provides values for types of headingsToCheck
	/// </summary>
	[Flags]
	public enum HeadingType
	{
		/// <summary>
		/// No headingsToCheck specified
		/// </summary>
		None = 0,
		/// <summary>
		/// Print heading
		/// </summary>
		Print = 1,
		/// <summary>
		/// Help heading
		/// </summary>
		Help = 2,
		/// <summary>
		/// Web heading
		/// </summary>
		Web = 4
	}

	/// <summary>
	/// Provides methods and data that support synchronizing topic headingsToCheck
	/// </summary>
	public class SyncHeadings
	{
		#region Fields (15) 

		/// <summary>
		/// Backing field for the <see cref="AitArgs"/> property.
		/// </summary>
		private AuthoritEventArgs aitArgs;
		/// <summary>
		/// Provides contextual data and methods for the current instance of the Author-it Editor application.
		/// </summary>
		private Ait.ApplicationContext appContext;
		/// <summary>
		/// Provides methods for logging in to the Author-it library.
		/// </summary>
		private Ait.Services.Authentication authService;
		/// <summary>
		/// Stores the name of the database that hosts the Author-it library.
		/// </summary>
		private string databaseName;
		/// <summary>
		/// Stores Author-it library configuration information.
		/// </summary>
		private Ait.Configuration.LibraryConfigurationList libraryConfigList;
		/// <summary>
		/// Backing field for the <see cref="LogFileName"/> property.
		/// </summary>
		private string logFileName = "syncheadings_${shortdate}.log";
		/// <summary>
		/// Instance of the class that provides data and methods for writing to a log.
		/// </summary>
		private LoggerWrapper logger;
		/// <summary>
		/// Stores the name of the logger for the application.
		/// </summary>
		private const string loggerName = "SyncHeadingsLogger";
		/// <summary>
		/// Backing field for the <see cref="LogMessageHeadingMatchesDescription"/> property.
		/// </summary>
		private string logMessageHeadingMatchesDescription = "{0} heading matches description";
		/// <summary>
		/// Backing field for the <see cref="LogMessageOverwrittenHeadingValue"/> property.
		/// </summary>
		private string logMessageOverwrittenHeadingValue = "{0} Heading overwritten from '{1}'";
		/// <summary>
		/// Backing field for the <see cref="LogMessageObjectNotUpdated"/> property.
		/// </summary>
		private string logMessageObjectNotUpdated = "Object not updated";
		/// <summary>
		/// Backing field for the <see cref="LogMessageSkippedNonEmptyHeading"/> property.
		/// </summary>
		private string logMessageSkippedNonEmptyHeading = "Non empty {0} heading = '{1}'";
		/// <summary>
		/// Backing field for the <see cref="LogMessageTopicIsTemplate"/> property.
		/// </summary>
		private string logMessageTopicIsTemplate = "Topic is a template";
		/// <summary>
		/// Stores the file name of the NLog configuration file.
		/// </summary>
		private string nlogConfigFileName = "NLog.config";
		/// <summary>
		/// Provides methods for getting and updating topics in the Author-it library.
		/// </summary>
		private Ait.Services.Topic topicService;

		#endregion Fields 

		#region Constructors (1) 

		/// <summary>
		/// Initializes a new instance of the <see cref="SyncHeadings"/> class.
		/// </summary>
		/// <param name="aitEventArgs">Arguments that contain library and user contexts</param>
		/// <param name="login">Specifies whether to attempt to log in to the library</param>
		public SyncHeadings(AuthoritEventArgs aitEventArgs, bool login)
		{
			logger = new LoggerWrapper(loggerName, nlogConfigFileName, logFileName);
			try
			{
				aitArgs = aitEventArgs;
				libraryConfigList = Ait.Configuration.PluginUtility.GetLibraryConfigurationList(aitArgs);
				databaseName = Ait.Configuration.PluginUtility.GetDatabaseName(aitArgs);
				appContext = new Ait.ApplicationContext(libraryConfigList);
				authService = appContext.GetServiceInstance<Ait.Services.Authentication>();
				topicService = appContext.GetServiceInstance<Ait.Services.Topic>();
				logger.WriteInfo(LogEventType.OperationSuccess);
			}
			catch (Exception ex)
			{
				logger.WriteFatalException(LogEventType.OperationFailed, ex);
				throw;
			}
			if (login)
			{
				LoginToLibrary();
			}
		}

		#endregion Constructors 

		#region Properties (8) 

		/// <summary>
		/// Gets the arguments that contain the library and user contexts
		/// </summary>
		public AuthoritEventArgs AitArgs
		{
			get { return aitArgs; }
		}

		/// <summary>
		/// Gets or sets the formatted name of the log file.
		/// </summary>
		/// <remarks>Default value is "syncheadings_${shortdate}.log".</remarks>
		public string LogFileName
		{
			get { return logFileName; }
			set { logFileName = value; }
		}

		/// <summary>
		/// Gets or sets the formatted message to use when logging that the heading already matches the description.
		/// </summary>
		/// <remarks>Default value is "{0} heading matches description".</remarks>
		public string LogMessageHeadingMatchesDescription
		{
			get { return logMessageHeadingMatchesDescription; }
			set { logMessageHeadingMatchesDescription = value; }
		}

		/// <summary>
		/// Gets or sets the string format to use when logging heading values.
		/// </summary>
		/// <remarks>The default value is "{0} Heading overwritten from '{1}'".</remarks>
		public string LogMessageOverwrittenHeadingValue
		{
			get { return logMessageOverwrittenHeadingValue; }
			set { logMessageOverwrittenHeadingValue = value; }
		}

		/// <summary>
		/// Gets or sets the string format to use when logging that no object update is needed.
		/// </summary>
		/// <remarks>The default value is "Object not updated".</remarks>
		public string LogMessageObjectNotUpdated
		{
			get { return logMessageObjectNotUpdated; }
			set { logMessageObjectNotUpdated = value; }
		}

		/// <summary>
		/// Gets or sets the formatted message to use when logging when a non-empty heading is not updated.
		/// </summary>
		/// <remarks>Default value is "Non empty {0} heading = '{1}'".</remarks>
		public string LogMessageSkippedNonEmptyHeading
		{
			get { return logMessageSkippedNonEmptyHeading; }
			set { logMessageSkippedNonEmptyHeading = value; }
		}

		/// <summary>
		/// Gets or sets the string to use when logging a warning that a selected topic is a template.
		/// </summary>
		/// <remarks>The default value is "Topic is a template".</remarks>
		public string LogMessageTopicIsTemplate
		{
			get { return logMessageTopicIsTemplate; }
			set { logMessageTopicIsTemplate = value; }
		}

		/// <summary>
		/// Gets or sets the filename of the NLog configuration file.
		/// </summary>
		/// <remarks>Default value is "NLog.config".</remarks>
		public string NlogConfigFileName
		{
			get { return nlogConfigFileName; }
			set { nlogConfigFileName = value; }
		}

		#endregion Properties 

		#region Methods (2) 

		// Public Methods (2) 

		/// <summary>
		/// Attempts to log in to the Author-it library
		/// </summary>
		/// <returns>Value indicating the result of the attempt to log in</returns>
		public Ait.Dto.LoginResult LoginToLibrary()
		{
			try
			{
				Ait.Dto.LoginResponse response = authService.Login(libraryConfigList[0].Alias, aitArgs.Username, aitArgs.Password);
				if (response.Result == Ait.Dto.LoginResult.Success)
				{
					logger.WriteLoginSuccess(aitArgs.Username, databaseName);
				}
				else
				{
					logger.WriteLoginFailed(aitArgs.Username, databaseName, response.Result.ToString());
				}
				return response.Result;
			}
			catch (Exception ex)
			{
				logger.WriteFatalException(LogEventType.OperationFailed, ex);
				throw;
			}
		}

		/// <summary>
		/// Synchronizes headingsToCheck in a topic with the description of the topic
		/// </summary>
		/// <param name="topicId">Object ID for the topic</param>
		/// <param name="headingsToCheck">Flags that specify which headingsToCheck to synchronize</param>
		/// <param name="overwriteExistingHeadings">Specifies whether to overwrite headings that have values</param>
		/// <returns>True if the method succeeded; otherwise, false</returns>
		public bool Synchronize(int topicId, HeadingType headingsToCheck, bool overwriteExistingHeadings)
		{
			bool isSuccess = false;
			bool loggedIn;
			loggedIn = authService.IsLoggedIn();
			if (!loggedIn)
			{
				Ait.Dto.LoginResult loginResult;
				loginResult = LoginToLibrary();
				if (loginResult == Ait.Dto.LoginResult.Success)
				{
					loggedIn = true;
				}
			}
			if (loggedIn)
			{
				Ait.Dto.Topic topic = new Ait.Dto.Topic();
				try
				{
					bool updateNeeded = false;
					topic = topicService.Get(topicId);
					HeadingType headingsUpdated = HeadingType.None;

					#region Check Print Heading

					if ((headingsToCheck & HeadingType.Print) == HeadingType.Print)
					{
						if (topic.PrintHeading == topic.Description)
						{
							logger.WriteInfo(LogEventType.Information, aitArgs.Username, topic.ObjectId, topic.Description, String.Format(logMessageHeadingMatchesDescription, HeadingType.Print));
						}
						else
						{
							if (!String.IsNullOrEmpty(topic.PrintHeading))
							{
								if (!overwriteExistingHeadings)
								{
									logger.WriteLog(LogLevel.Warn, LogEventType.Information, aitArgs.Username, topic.ObjectId, topic.Description, String.Format(logMessageSkippedNonEmptyHeading, HeadingType.Print, topic.PrintHeading));
								}
								else
								{
									logger.WriteLog(LogLevel.Warn, LogEventType.Information, aitArgs.Username, topic.ObjectId, topic.Description, String.Format(logMessageOverwrittenHeadingValue, HeadingType.Print, topic.PrintHeading));
									topic.PrintHeading = topic.Description;
									headingsUpdated |= HeadingType.Print;
									updateNeeded = true;
								}
							}
							else
							{
								topic.PrintHeading = topic.Description;
								headingsUpdated |= HeadingType.Print;
								updateNeeded = true;
							}
						}
					}
					
					#endregion

					#region Check Help Heading

					if ((headingsToCheck & HeadingType.Help) == HeadingType.Help)
					{
						if (topic.HelpHeading == topic.Description)
						{
							logger.WriteInfo(LogEventType.Information, aitArgs.Username, topic.ObjectId, topic.Description, String.Format(logMessageHeadingMatchesDescription, HeadingType.Help));
						}
						else
						{
							if (!String.IsNullOrEmpty(topic.HelpHeading))
							{
								if (!overwriteExistingHeadings)
								{
									logger.WriteLog(LogLevel.Warn, LogEventType.Information, aitArgs.Username, topic.ObjectId, topic.Description, String.Format(logMessageSkippedNonEmptyHeading, HeadingType.Help, topic.HelpHeading));
								}
								else
								{
									logger.WriteLog(LogLevel.Warn, LogEventType.Information, aitArgs.Username, topic.ObjectId, topic.Description, String.Format(logMessageOverwrittenHeadingValue, HeadingType.Help, topic.HelpHeading));
									topic.HelpHeading = topic.Description;
									headingsUpdated |= HeadingType.Help;
									updateNeeded = true;
								}
							}
							else
							{
								topic.HelpHeading = topic.Description;
								headingsUpdated |= HeadingType.Help;
								updateNeeded = true;
							}
						}
					}

					#endregion

					#region Check Web Heading

					if ((headingsToCheck & HeadingType.Web) == HeadingType.Web)
					{
						if (topic.WebHeading == topic.Description)
						{
							logger.WriteInfo(LogEventType.Information, aitArgs.Username, topic.ObjectId, topic.Description, String.Format(logMessageHeadingMatchesDescription, HeadingType.Web));
						}
						else
						{
							if (!String.IsNullOrEmpty(topic.WebHeading))
							{
								if (!overwriteExistingHeadings)
								{
									logger.WriteLog(LogLevel.Warn, LogEventType.Information, aitArgs.Username, topic.ObjectId, topic.Description, String.Format(logMessageSkippedNonEmptyHeading, HeadingType.Web, topic.WebHeading));
								}
								else
								{
									logger.WriteLog(LogLevel.Warn, LogEventType.Information, aitArgs.Username, topic.ObjectId, topic.Description, String.Format(logMessageOverwrittenHeadingValue, HeadingType.Web, topic.WebHeading));
									topic.WebHeading = topic.Description;
									headingsUpdated |= HeadingType.Web;
									updateNeeded = true;
								}
							}
							else
							{
								topic.WebHeading = topic.Description;
								headingsUpdated |= HeadingType.Web;
								updateNeeded = true;
							}
						}
					}
					
					#endregion

					if (updateNeeded)
					{
						if (!topic.IsTemplate)
						{
							topicService.Update(topic);
							logger.WriteInfo(LogEventType.ObjectUpdate, aitArgs.Username, topic.ObjectId, topic.Description, headingsUpdated.ToString());
						}
						else
						{
							logger.WriteLog(LogLevel.Warn, LogEventType.OperationCancelled, aitArgs.Username, topic.ObjectId, topic.Description, logMessageTopicIsTemplate);
						}
					}
					else
					{
						logger.WriteInfo(LogEventType.Information, aitArgs.Username, topic.ObjectId, topic.Description, logMessageObjectNotUpdated);
					}
					isSuccess = true;
				}
				catch (Exception ex)
				{
					logger.WriteLog(LogLevel.Error, LogEventType.OperationFailed, aitArgs.Username, topicId, topic.Description, ex.Message);
				}
			}
			else
			{
				logger.WriteLog(LogLevel.Error, LogEventType.OperationFailed, aitArgs.Username, null, String.Empty);
			}
			return isSuccess;
		}

		#endregion Methods 
	}
}
